import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import re
import os


class BibTexManager:
    def __init__(self, root):
        self.root = root
        self.root.title("BibTeX引用管理器")
        self.root.geometry("900x700")

        # 变量初始化
        self.tex_files = []
        self.bib_file = ""
        self.cited_ids = []
        self.bib_entries = {}
        self.duplicate_entries = {}

        # 创建UI界面
        self.create_widgets()

    def create_widgets(self):
        # 文件选择框架
        file_frame = ttk.LabelFrame(self.root, text="文件选择")
        file_frame.pack(fill="x", padx=10, pady=5)

        # TeX文件选择和管理
        ttk.Label(file_frame, text="TeX文件:").grid(row=0, column=0, sticky="w", padx=5, pady=5)

        # TeX文件列表框
        self.tex_files_listbox = tk.Listbox(file_frame, width=60, height=5)
        self.tex_files_listbox.grid(row=0, column=1, rowspan=3, padx=5, pady=5, sticky="nsew")

        # 滚动条
        tex_scrollbar = ttk.Scrollbar(file_frame, orient="vertical", command=self.tex_files_listbox.yview)
        tex_scrollbar.grid(row=0, column=2, rowspan=3, sticky="ns")
        self.tex_files_listbox.configure(yscrollcommand=tex_scrollbar.set)

        # TeX文件管理按钮
        tex_buttons_frame = ttk.Frame(file_frame)
        tex_buttons_frame.grid(row=0, column=3, rowspan=3, padx=5, pady=5, sticky="ns")

        ttk.Button(tex_buttons_frame, text="添加", command=self.add_tex_files).pack(fill="x", pady=2)
        ttk.Button(tex_buttons_frame, text="上移", command=self.move_tex_file_up).pack(fill="x", pady=2)
        ttk.Button(tex_buttons_frame, text="下移", command=self.move_tex_file_down).pack(fill="x", pady=2)
        ttk.Button(tex_buttons_frame, text="删除", command=self.delete_tex_file).pack(fill="x", pady=2)

        # BibTeX文件选择
        ttk.Label(file_frame, text="BibTeX文件:").grid(row=4, column=0, sticky="w", padx=5, pady=5)
        self.bib_file_var = tk.StringVar()
        ttk.Entry(file_frame, textvariable=self.bib_file_var, width=60, state="readonly").grid(row=4, column=1, padx=5,
                                                                                               pady=5)
        ttk.Button(file_frame, text="浏览...", command=self.select_bib_file).grid(row=4, column=3, padx=5, pady=5,
                                                                                sticky="w")

        # 处理框架
        process_frame = ttk.LabelFrame(self.root, text="处理")
        process_frame.pack(fill="both", expand=True, padx=10, pady=5)

        # 显示提取的引用ID的文本区域
        ttk.Label(process_frame, text="提取的引用ID:").grid(row=0, column=0, sticky="w", padx=5, pady=5)
        self.citations_text = tk.Text(process_frame, height=10, width=80)
        self.citations_text.grid(row=1, column=0, padx=5, pady=5, columnspan=3)
        scrollbar1 = ttk.Scrollbar(process_frame, orient="vertical", command=self.citations_text.yview)
        scrollbar1.grid(row=1, column=3, sticky="ns")
        self.citations_text.configure(yscrollcommand=scrollbar1.set)

        # 处理按钮
        button_frame = ttk.Frame(self.root)
        button_frame.pack(fill="x", padx=10, pady=5)

        ttk.Button(button_frame, text="提取引用", command=self.extract_citations).pack(side="left", padx=5, pady=5)
        ttk.Button(button_frame, text="处理BibTeX", command=self.process_bibtex).pack(side="left", padx=5, pady=5)
        ttk.Button(button_frame, text="处理重复项", command=self.handle_duplicates, state="disabled").pack(side="left",
                                                                                                      padx=5, pady=5)
        ttk.Button(button_frame, text="保存为新文件", command=lambda: self.save_filtered_bib(False)).pack(side="right",
                                                                                                    padx=5, pady=5)
        ttk.Button(button_frame, text="覆盖原文件", command=lambda: self.save_filtered_bib(True)).pack(side="right", padx=5,
                                                                                                  pady=5)

    def add_tex_files(self):
        """添加多个TeX文件到列表"""
        files = filedialog.askopenfilenames(
            title="选择TeX文件",
            filetypes=[("TeX文件", "*.tex"), ("所有文件", "*.*")]
        )
        if files:
            for file in files:
                if file not in self.tex_files:
                    self.tex_files.append(file)
                    self.tex_files_listbox.insert(tk.END, os.path.basename(file))

    def move_tex_file_up(self):
        """将选中的TeX文件向上移动"""
        selected_idx = self.tex_files_listbox.curselection()
        if not selected_idx:
            messagebox.showinfo("提示", "请先选择一个文件")
            return

        idx = selected_idx[0]
        if idx > 0:
            # 在列表中交换位置
            self.tex_files[idx], self.tex_files[idx - 1] = self.tex_files[idx - 1], self.tex_files[idx]

            # 在列表框中更新显示
            filename = self.tex_files_listbox.get(idx)
            self.tex_files_listbox.delete(idx)
            self.tex_files_listbox.insert(idx - 1, filename)
            self.tex_files_listbox.selection_clear(0, tk.END)
            self.tex_files_listbox.selection_set(idx - 1)
            self.tex_files_listbox.see(idx - 1)

    def move_tex_file_down(self):
        """将选中的TeX文件向下移动"""
        selected_idx = self.tex_files_listbox.curselection()
        if not selected_idx:
            messagebox.showinfo("提示", "请先选择一个文件")
            return

        idx = selected_idx[0]
        if idx < len(self.tex_files) - 1:
            # 在列表中交换位置
            self.tex_files[idx], self.tex_files[idx + 1] = self.tex_files[idx + 1], self.tex_files[idx]

            # 在列表框中更新显示
            filename = self.tex_files_listbox.get(idx)
            self.tex_files_listbox.delete(idx)
            self.tex_files_listbox.insert(idx + 1, filename)
            self.tex_files_listbox.selection_clear(0, tk.END)
            self.tex_files_listbox.selection_set(idx + 1)
            self.tex_files_listbox.see(idx + 1)

    def delete_tex_file(self):
        """删除选中的TeX文件"""
        selected_idx = self.tex_files_listbox.curselection()
        if not selected_idx:
            messagebox.showinfo("提示", "请先选择一个文件")
            return

        idx = selected_idx[0]
        del self.tex_files[idx]
        self.tex_files_listbox.delete(idx)

        # 选择删除后的位置
        if self.tex_files:
            new_idx = min(idx, len(self.tex_files) - 1)
            self.tex_files_listbox.selection_set(new_idx)
            self.tex_files_listbox.see(new_idx)

    def select_bib_file(self):
        """选择BibTeX文件"""
        self.bib_file = filedialog.askopenfilename(
            title="选择BibTeX文件",
            filetypes=[("BibTeX文件", "*.bib"), ("所有文件", "*.*")]
        )
        if self.bib_file:
            self.bib_file_var.set(self.bib_file)

    def extract_citations(self):
        """从TeX文件中提取引用ID"""
        if not self.tex_files:
            messagebox.showwarning("警告", "请先选择至少一个TeX文件。")
            return

        self.cited_ids = []
        # 匹配\cite{...}, \citep{...}, \citet{...}等
        citation_pattern = r'\\cite[a-zA-Z]?\{([^}]+)\}'

        # 显示处理进度
        progress_window = tk.Toplevel(self.root)
        progress_window.title("处理中")
        progress_window.geometry("300x100")
        progress_window.transient(self.root)
        progress_window.grab_set()

        ttk.Label(progress_window, text="正在处理TeX文件...").pack(pady=10)
        progress_var = tk.IntVar()
        progress_bar = ttk.Progressbar(progress_window, variable=progress_var, maximum=len(self.tex_files))
        progress_bar.pack(fill='x', padx=20, pady=10)

        progress_window.update()

        for i, tex_file in enumerate(self.tex_files):
            try:
                with open(tex_file, 'r', encoding='utf-8') as file:
                    content = file.read()

                    # 查找所有引用标签
                    citations = re.findall(citation_pattern, content)

                    # 处理每个引用(可能包含多个以逗号分隔的ID)
                    for citation in citations:
                        ids = [id.strip() for id in citation.split(',')]
                        self.cited_ids.extend(ids)

                # 更新进度条
                progress_var.set(i + 1)
                progress_window.update()

            except Exception as e:
                progress_window.destroy()
                messagebox.showerror("错误", f"处理{os.path.basename(tex_file)}时出错: {str(e)}")
                return

        progress_window.destroy()

        # 去除重复项但保持原始顺序（保留第一次出现的位置）
        self.cited_ids = list(dict.fromkeys(self.cited_ids))

        # 显示提取的ID
        self.citations_text.delete(1.0, tk.END)
        self.citations_text.insert(tk.END, ", ".join(self.cited_ids))

        messagebox.showinfo("信息", f"已提取{len(self.cited_ids)}个唯一引用ID。")

    def extract_field(self, content, field_name):
        """从BibTeX条目内容中提取字段"""
        field_pattern = fr'{field_name}\s*=\s*(?:\{{(.*?)\}}|([\w-]+))'
        match = re.search(field_pattern, content, re.IGNORECASE | re.DOTALL)
        if match:
            return match.group(1) if match.group(1) else match.group(2)
        return ""

    def process_bibtex(self):
        """处理BibTeX文件"""
        if not self.bib_file:
            messagebox.showwarning("警告", "请先选择BibTeX文件。")
            return

        try:
            # 读取BibTeX文件
            with open(self.bib_file, 'r', encoding='utf-8') as bibtex_file:
                content = bibtex_file.read()

            # 重置条目
            self.bib_entries = {}
            self.duplicate_entries = {}

            # 使用更健壮的方法解析BibTeX条目
            # 这个正则表达式寻找@type{id, ... }模式，正确处理嵌套大括号
            entry_pattern = r'@(\w+)\s*\{\s*([^,]+),\s*([^@]+?)(?=\s*@|\s*\Z)'

            entries = []
            for match in re.finditer(entry_pattern, content, re.DOTALL):
                entry_type = match.group(1)
                entry_id = match.group(2).strip()
                entry_content = match.group(3)

                # 获取用于显示的重要字段
                title = self.extract_field(entry_content, 'title')
                author = self.extract_field(entry_content, 'author')
                year = self.extract_field(entry_content, 'year')

                entries.append({
                    'id': entry_id,
                    'type': entry_type,
                    'title': title,
                    'author': author,
                    'year': year,
                    'content': entry_content,
                    'raw': match.group(0)
                })

            # 存储条目并识别重复项
            for entry in entries:
                entry_id = entry['id']
                if entry_id in self.bib_entries:
                    # 这是一个重复项
                    if entry_id not in self.duplicate_entries:
                        self.duplicate_entries[entry_id] = [self.bib_entries[entry_id]]
                    self.duplicate_entries[entry_id].append(entry)
                else:
                    self.bib_entries[entry_id] = entry

            # 更新处理重复项按钮状态
            for child in self.root.winfo_children():
                if isinstance(child, ttk.Frame):
                    for button in child.winfo_children():
                        if isinstance(button, ttk.Button) and button['text'] == "处理重复项":
                            if self.duplicate_entries:
                                button['state'] = 'normal'
                            else:
                                button['state'] = 'disabled'

            # 显示处理结果
            if self.duplicate_entries:
                messagebox.showinfo("发现重复项", f"在BibTeX文件中发现{len(self.duplicate_entries)}个重复条目。\n请点击'处理重复项'按钮进行处理。")
            else:
                messagebox.showinfo("无重复项", "BibTeX文件中没有发现重复条目。")

        except Exception as e:
            messagebox.showerror("错误", f"处理BibTeX文件时出错: {str(e)}")

    def handle_duplicates(self):
        """处理重复的BibTeX条目"""
        if not self.duplicate_entries:
            messagebox.showinfo("信息", "没有重复条目需要处理")
            return

        # 创建一个新窗口来处理重复项
        dup_window = tk.Toplevel(self.root)
        dup_window.title("处理重复条目")
        dup_window.geometry("900x600")
        dup_window.transient(self.root)
        dup_window.grab_set()

        # 获取重复条目的ID列表
        dup_ids = list(self.duplicate_entries.keys())
        current_index = [0]  # 使用列表以便在嵌套函数中修改

        # 创建用于显示当前处理的重复条目ID
        id_frame = ttk.Frame(dup_window)
        id_frame.pack(fill='x', padx=10, pady=5)

        ttk.Label(id_frame, text="当前处理的重复ID:").pack(side="left")
        id_var = tk.StringVar()
        id_var.set(dup_ids[0])
        ttk.Label(id_frame, textvariable=id_var, font=("TkDefaultFont", 10, "bold")).pack(side="left", padx=5)

        ttk.Label(id_frame, text=f"(第1组，共{len(dup_ids)}组)").pack(side="left")

        # 用于显示条目内容的框架
        entries_frame = ttk.Frame(dup_window)
        entries_frame.pack(fill="both", expand=True, padx=10, pady=5)

        # 选择要保留的条目的变量
        selected_var = tk.IntVar()
        selected_var.set(0)  # 默认选择第一个

        # 存储所有选择的结果
        selections = {}

        def update_display():
            """更新显示内容"""
            # 清空现有的内容
            for widget in entries_frame.winfo_children():
                widget.destroy()

            current_id = dup_ids[current_index[0]]
            id_var.set(current_id)
            entries = self.duplicate_entries[current_id]

            # 更新当前位置标签
            position_label.config(text=f"(第{current_index[0] + 1}组，共{len(dup_ids)}组)")

            # 恢复之前的选择（如果有）
            if current_id in selections:
                selected_var.set(selections[current_id])
            else:
                selected_var.set(0)

            # 为每个条目创建一个框架
            for i, entry in enumerate(entries):
                entry_frame = ttk.LabelFrame(entries_frame, text=f"版本 #{i + 1}")
                entry_frame.pack(fill="both", expand=True, padx=5, pady=5)

                # 添加选择按钮
                ttk.Radiobutton(entry_frame, text="选择此版本", variable=selected_var, value=i).pack(anchor="w", padx=5,
                                                                                                pady=2)

                # 显示条目基本信息
                info_frame = ttk.Frame(entry_frame)
                info_frame.pack(fill="x", padx=5, pady=2)

                ttk.Label(info_frame, text="类型: ").grid(row=0, column=0, sticky="w")
                ttk.Label(info_frame, text=entry['type']).grid(row=0, column=1, sticky="w")

                ttk.Label(info_frame, text="标题: ").grid(row=1, column=0, sticky="w")
                ttk.Label(info_frame, text=entry['title']).grid(row=1, column=1, sticky="w")

                ttk.Label(info_frame, text="作者: ").grid(row=2, column=0, sticky="w")
                ttk.Label(info_frame, text=entry['author']).grid(row=2, column=1, sticky="w")

                ttk.Label(info_frame, text="年份: ").grid(row=3, column=0, sticky="w")
                ttk.Label(info_frame, text=entry['year']).grid(row=3, column=1, sticky="w")

                # 显示完整的条目内容
                content_frame = ttk.Frame(entry_frame)
                content_frame.pack(fill="both", expand=True, padx=5, pady=5)

                ttk.Label(content_frame, text="完整条目:").pack(anchor="w")

                text_area = tk.Text(content_frame, height=8, width=80, wrap="word")
                text_area.pack(fill="both", expand=True, padx=2, pady=2)

                # 添加滚动条
                scrollbar = ttk.Scrollbar(text_area, orient="vertical", command=text_area.yview)
                scrollbar.pack(side="right", fill="y")
                text_area.configure(yscrollcommand=scrollbar.set)

                # 填充条目内容
                full_content = f"@{entry['type']}{{{entry['id']},\n{entry['content']}}}"
                text_area.insert("1.0", full_content)
                text_area.configure(state="disabled")  # 设置为只读

        def next_entry():
            """处理下一个重复条目"""
            # 保存当前选择
            current_id = dup_ids[current_index[0]]
            selections[current_id] = selected_var.get()

            # 移动到下一个
            if current_index[0] < len(dup_ids) - 1:
                current_index[0] += 1
                update_display()
            else:
                messagebox.showinfo("完成", "已完成所有重复条目的处理。")

        def prev_entry():
            """处理上一个重复条目"""
            # 保存当前选择
            current_id = dup_ids[current_index[0]]
            selections[current_id] = selected_var.get()

            # 移动到上一个
            if current_index[0] > 0:
                current_index[0] -= 1
                update_display()

        def finish_selection():
            """完成选择并关闭窗口"""
            # 保存当前选择
            current_id = dup_ids[current_index[0]]
            selections[current_id] = selected_var.get()

            # 保存所有选择到应用状态
            self.duplicate_selections = selections
            dup_window.destroy()

        # 导航和确认按钮
        nav_frame = ttk.Frame(dup_window)
        nav_frame.pack(fill="x", padx=10, pady=10)

        position_label = ttk.Label(nav_frame, text=f"(第1组，共{len(dup_ids)}组)")

        ttk.Button(nav_frame, text="上一组", command=prev_entry).pack(side="left", padx=5)
        ttk.Button(nav_frame, text="下一组", command=next_entry).pack(side="left", padx=5)
        ttk.Button(nav_frame, text="完成选择", command=finish_selection).pack(side="right", padx=5)

        # 初始化显示
        update_display()

    def save_filtered_bib(self, overwrite=False):
        """保存过滤后的BibTeX文件"""
        if not self.bib_file or not self.bib_entries:
            messagebox.showwarning("警告", "请先选择并处理BibTeX文件。")
            return

        if not self.cited_ids:
            messagebox.showwarning("警告", "请先提取引用。")
            return

        try:
            # 获取选定要保留的重复项
            duplicate_selections = getattr(self, 'duplicate_selections', {})

            # 构建过滤后的BibTeX内容
            filtered_content = ""
            saved_entries = {}

            # 按引用顺序处理条目
            for cite_id in self.cited_ids:
                if cite_id in self.bib_entries:
                    # 检查是否为重复项，如果是则使用选定的版本
                    if cite_id in self.duplicate_entries:
                        idx = duplicate_selections.get(cite_id, 0)
                        entry = self.duplicate_entries[cite_id][idx]
                    else:
                        entry = self.bib_entries[cite_id]

                    filtered_content += entry['raw'] + "\n\n"
                    saved_entries[cite_id] = True
                else:
                    # 在BibTeX文件中未找到引用
                    messagebox.showwarning("警告", f"BibTeX文件中未找到引用{cite_id}。")

            # 写入文件
            if overwrite:
                output_file = self.bib_file
                confirm = messagebox.askyesno("确认", "确定要覆盖原始文件吗？")
                if not confirm:
                    return
            else:
                # 请求保存位置
                output_file = filedialog.asksaveasfilename(
                    title="保存过滤后的BibTeX文件",
                    defaultextension=".bib",
                    filetypes=[("BibTeX文件", "*.bib"), ("所有文件", "*.*")],
                    initialdir=os.path.dirname(self.bib_file),
                    initialfile=f"filtered_{os.path.basename(self.bib_file)}"
                )
                if not output_file:
                    return

            with open(output_file, 'w', encoding='utf-8') as bibtex_file:
                bibtex_file.write(filtered_content)

            messagebox.showinfo("成功", f"已保存包含{len(saved_entries)}个条目的过滤后BibTeX文件到{output_file}。")
        except Exception as e:
            messagebox.showerror("错误", f"保存过滤后的BibTeX文件时出错: {str(e)}")


if __name__ == "__main__":
    root = tk.Tk()
    app = BibTexManager(root)
    root.mainloop()